<?php

if (!defined('BASEPATH'))
    exit('No direct script access allowed');

class CI_Template {

    var $CI;
    var $config;
    var $template;
    var $master;
    var $regions = array(
        '_doctype' => array(),
        '_title' => array(),
        '_scripts' => array(),
        '_styles' => array(),
        '_meta' => array()
    );
    var $output;
    var $doctype = array();
    var $js = array();
    var $css = array();
    var $title = array();
    var $meta = array();
    var $parser = 'parser';
    var $parser_method = 'parse';
    var $parse_template = FALSE;

    /**
     * Constructor
     *
     * Loads template configuration, template regions, and validates existence of
     * default template
     *
     * @access	public
     */
    function CI_Template() {
        // Copy an instance of CI so we can use the entire framework.
        $this->CI = & get_instance();

        // Load the template config file and setup our master template and regions
        include(APPPATH . 'config/template' . EXT);
        if (isset($template)) {
            $this->config = $template;
        }
    }

    // --------------------------------------------------------------------

    /**
     * Use given template settings
     *
     * @access  public
     * @param   string   array key to access template settings
     * @return  void
     */
    function set_template($group) {
        if (isset($this->config[$group])) {
            $this->template = $this->config[$group];
        } else {
            show_error('The "' . $group . '" template group does not exist. Provide a valid group name or add the group first.');
        }
        $this->initialize($this->template);
    }

    // --------------------------------------------------------------------

    /**
     * Set master template
     *
     * @access  public
     * @param   string   filename of new master template file
     * @return  void
     */
    function set_master_template($filename) {
        if (file_exists(APPPATH . 'views/' . $filename) or file_exists(APPPATH . 'views/' . $filename . EXT)) {
            $this->master = $filename;
        } else {
            show_error('The filename provided does not exist in <strong>' . APPPATH . 'views</strong>. Remember to include the extension if other than ".php"');
        }
    }

    // --------------------------------------------------------------------

    /**
     * Dynamically add a template and optionally switch to it
     *
     * @access  public
     * @param   string   array key to access template settings
     * @param   array properly formed
     * @return  void
     */
    function add_template($group, $template, $activate = FALSE) {
        if (!isset($this->config[$group])) {
            $this->config[$group] = $template;
            if ($activate === TRUE) {
                $this->initialize($template);
            }
        } else {
            show_error('The "' . $group . '" template group already exists. Use a different group name.');
        }
    }

    // --------------------------------------------------------------------

    /**
     * Initialize class settings using config settings
     *
     * @access  public
     * @param   array   configuration array
     * @return  void
     */
    function initialize($props) {
        // Set master template
        if (isset($props['template'])
                && (file_exists(APPPATH . 'views/' . $props['template']) or file_exists(APPPATH . 'views/' . $props['template'] . EXT))) {
            $this->master = $props['template'];
        } else {
            // Master template must exist. Throw error.
            show_error('Either you have not provided a master template or the one provided does not exist in <strong>' . APPPATH . 'views</strong>. Remember to include the extension if other than ".php"');
        }

        // Load our regions
        if (isset($props['regions'])) {
            $this->set_regions($props['regions']);
        }

        // Set parser and parser method
        if (isset($props['parser'])) {
            $this->set_parser($props['parser']);
        }
        if (isset($props['parser_method'])) {
            $this->set_parser_method($props['parser_method']);
        }

        // Set master template parser instructions
        $this->parse_template = isset($props['parse_template']) ? $props['parse_template'] : FALSE;
    }

    // --------------------------------------------------------------------

    /**
     * Set regions for writing to
     *
     * @access  public
     * @param   array   properly formed regions array
     * @return  void
     */
    function set_regions($regions) {
        if (count($regions)) {
            $this->regions = array(
                '_doctype' => array(),
                '_title' => array(),
                '_scripts' => array(),
                '_styles' => array(),
                '_meta' => array()
            );
            foreach ($regions as $key => $region) {
                // Regions must be arrays, but we take the burden off the template
                // developer and insure it here
                if (!is_array($region)) {
                    $this->add_region($region);
                } else {
                    $this->add_region($key, $region);
                }
            }
        }
    }

    // --------------------------------------------------------------------

    /**
     * Dynamically add region to the currently set template
     *
     * @access  public
     * @param   string   Name to identify the region
     * @param   array Optional array with region defaults
     * @return  void
     */
    function add_region($name, $props = array()) {
        if (!is_array($props)) {
            $props = array();
        }

        if (!isset($this->regions[$name])) {
            $this->regions[$name] = $props;
        } else {
            show_error('The "' . $name . '" region has already been defined.');
        }
    }

    // --------------------------------------------------------------------

    /**
     * Empty a region's content
     *
     * @access  public
     * @param   string   Name to identify the region
     * @return  void
     */
    function empty_region($name) {
        if (isset($this->regions[$name]['content'])) {
            $this->regions[$name]['content'] = array();
        } else {
            show_error('The "' . $name . '" region is undefined.');
        }
    }

    // --------------------------------------------------------------------

    /**
     * Set parser
     *
     * @access  public
     * @param   string   name of parser class to load and use for parsing methods
     * @return  void
     */
    function set_parser($parser, $method = NULL) {
        $this->parser = $parser;
        $this->CI->load->library($parser);

        if ($method) {
            $this->set_parser_method($method);
        }
    }

    // --------------------------------------------------------------------

    /**
     * Set parser method
     *
     * @access  public
     * @param   string   name of parser class member function to call when parsing
     * @return  void
     */
    function set_parser_method($method) {
        $this->parser_method = $method;
    }

    // --------------------------------------------------------------------

    /**
     * Write contents to a region
     *
     * @access	public
     * @param	string	region to write to
     * @param	string	what to write
     * @param	boolean	FALSE to append to region, TRUE to overwrite region
     * @return	void
     */
    function write($region, $content, $overwrite = FALSE) {
        if (isset($this->regions[$region])) {
            if ($overwrite === TRUE) { // Should we append the content or overwrite it
                $this->regions[$region]['content'] = array($content);
            } else {
                $this->regions[$region]['content'][] = $content;
            }
        }

        // Regions MUST be defined
        else {
            show_error("Cannot write to the '{$region}' region. The region is undefined.");
        }
    }

    // --------------------------------------------------------------------

    /**
     * Write content from a View to a region. 'Views within views'
     *
     * @access	public
     * @param	string	region to write to
     * @param	string	view file to use
     * @param	array	variables to pass into view
     * @param	boolean	FALSE to append to region, TRUE to overwrite region
     * @return	void
     */
    function write_view($region, $view, $data = NULL, $overwrite = FALSE) {
        $args = func_get_args();

        // Get rid of non-views
        unset($args[0], $args[2], $args[3]);

        // Do we have more view suggestions?
        if (count($args) > 1) {
            foreach ($args as $suggestion) {
                if (file_exists(APPPATH . 'views/' . $suggestion . EXT) or file_exists(APPPATH . 'views/' . $suggestion)) {
                    // Just change the $view arg so the rest of our method works as normal
                    $view = $suggestion;
                    break;
                }
            }
        }

        $content = $this->CI->load->view($view, $data, TRUE);
        $this->write($region, $content, $overwrite);
    }

    // --------------------------------------------------------------------

    /**
     * Parse content from a View to a region with the Parser Class
     *
     * @access  public
     * @param   string   region to write to
     * @param   string   view file to parse
     * @param   array variables to pass into view for parsing
     * @param   boolean  FALSE to append to region, TRUE to overwrite region
     * @return  void
     */
    function parse_view($region, $view, $data = NULL, $overwrite = FALSE) {
        $this->CI->load->library('parser');

        $args = func_get_args();

        // Get rid of non-views
        unset($args[0], $args[2], $args[3]);

        // Do we have more view suggestions?
        if (count($args) > 1) {
            foreach ($args as $suggestion) {
                if (file_exists(APPPATH . 'views/' . $suggestion . EXT) or file_exists(APPPATH . 'views/' . $suggestion)) {
                    // Just change the $view arg so the rest of our method works as normal
                    $view = $suggestion;
                    break;
                }
            }
        }

        $content = $this->CI->{$this->parser}->{$this->parser_method}($view, $data, TRUE);
        $this->write($region, $content, $overwrite);
    }

    // --------------------------------------------------------------------

    /**
     * Dynamically include javascript in the template
     *
     * NOTE: This function does NOT check for existence of .js file
     *
     * @access  public
     * @param   string   script to import or embed
     * @param   string  'import' to load external file or 'embed' to add as-is
     * @param   boolean  TRUE to use 'defer' attribute, FALSE to exclude it
     * @return  TRUE on success, FALSE otherwise
     */
    function add_js($script, $type = 'import', $defer = FALSE) {
        $success = TRUE;
        $js = NULL;

        $this->CI->load->helper('url');

        switch ($type) {
            case 'import':
                $filepath = base_url() . $script;                
                $js = '<script type="text/javascript" src="' . $filepath . '"';
                if ($defer) {
                    $js .= ' defer="defer"';
                }
                $js .= "></script>";
                break;

            case 'embed':
                $js = '<script type="text/javascript"';
                if ($defer) {
                    $js .= ' defer="defer"';
                }
                $js .= ">";
                $js .= $script;
                $js .= '</script>';
                break;

            default:
                $success = FALSE;
                break;
        }

        // Add to js array if it doesn't already exist
        if ($js != NULL && !in_array($js, $this->js)) {
            $this->js[] = $js;
            $this->write('_scripts', $js);
        }

        return $success;
    }

    // --------------------------------------------------------------------

    /**
     * Dynamically include CSS in the template
     *
     * NOTE: This function does NOT check for existence of .css file
     *
     * @access  public
     * @param   string   CSS file to link, import or embed
     * @param   string  'link', 'import' or 'embed'
     * @param   string  media attribute to use with 'link' type only, FALSE for none
     * @return  TRUE on success, FALSE otherwise
     */
    function add_css($style, $type = 'link', $media = FALSE) {
        $success = TRUE;
        $css = NULL;
        $this->CI->load->helper('url');
        $filepath = base_url() . $style;

        switch ($type) {
            case 'link':

                $css = '<link type="text/css" rel="stylesheet" href="' . $filepath . '"';
                if ($media) {
                    $css .= ' media="' . $media . '"';
                }
                $css .= ' />';
                break;

            case 'import':
                $css = '<style type="text/css">@import url(' . $filepath . ');</style>';
                break;

            case 'embed':
                $css = '<style type="text/css">';
                $css .= $style;
                $css .= '</style>';
                break;

            default:
                $success = FALSE;
                break;
        }

        // Add to js array if it doesn't already exist
        if ($css != NULL && !in_array($css, $this->css)) {
            $this->css[] = $css;
            $this->write('_styles', $css);
        }

        return $success;
    }

    public function add_meta($name, $content) {
        $meta = '<meta name="' . $name . '" content="' . $content . '" />';
        if ($meta != NULL && !in_array($meta, $this->meta)) {
            $this->meta[] = $meta;
            $this->write('_meta', $meta);
        }
    }

    public function add_doctype($type = '') {
        $doctype = '';
        switch ($type) {
            case 'XHTML11':
                $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
                break;
            case 'XHTML1_STRICT':
                $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
                break;
            case 'XHTML1_TRANSITIONAL':
                $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
                break;
            case 'XHTML1_FRAMESET':
                $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">';
                break;
            case 'XHTML1_RDFA':
                $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">';
                break;
            case 'XHTML_BASIC1':
                $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">';
                break;
            case 'XHTML5':
                $doctype = '<!DOCTYPE html>';
                break;
            case 'HTML4_STRICT':
                $doctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">';
                break;
            case 'HTML4_LOOSE':
                $doctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">';
                break;
            case 'HTML4_FRAMESET':
                $doctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">';
                break;
            default:
                $doctype = '<!DOCTYPE html>';
        }
        if ($doctype != NULL && !in_array($doctype, $this->doctype)) {
            $this->doctype[] = $doctype;
            $this->write('_doctype', $doctype);
        }
    }

    public function add_title($name) {
        $title = '<title>' . $name . '</title>';
        // Add to js array if it doesn't already exist
        if ($title != NULL && !in_array($title, $this->title)) {
            $this->title[] = $title;
            $this->write('_title', $title);
        }
    }

    // --------------------------------------------------------------------

    /**
     * Render the master template or a single region
     *
     * @access	public
     * @param	string	optionally opt to render a specific region
     * @param	boolean	FALSE to output the rendered template, TRUE to return as a string. Always TRUE when $region is supplied
     * @return	void or string (result of template build)
     */
    function render($region = NULL, $buffer = FALSE, $parse = FALSE) {
        // Just render $region if supplied
        if ($region) { // Display a specific regions contents
            if (isset($this->regions[$region])) {
                $output = $this->_build_content($this->regions[$region]);
            } else {
                show_error("Cannot render the '{$region}' region. The region is undefined.");
            }
        }

        // Build the output array
        else {
            foreach ($this->regions as $name => $region) {
                $this->output[$name] = $this->_build_content($region);
            }

            if ($this->parse_template === TRUE or $parse === TRUE) {
                // Use provided parser class and method to render the template
                $output = $this->CI->{$this->parser}->{$this->parser_method}($this->master, $this->output, TRUE);

                // Parsers never handle output, but we need to mimick it in this case
                if ($buffer === FALSE) {
                    $this->CI->output->set_output($output);
                }
            } else {
                // Use CI's loader class to render the template with our output array
                $output = $this->CI->load->view($this->master, $this->output, $buffer);
            }
        }

        return $output;
    }

    // --------------------------------------------------------------------

    /**
     * Load the master template or a single region
     *
     * DEPRECATED!
     *
     * Use render() to compile and display your template and regions
     */
    function load($region = NULL, $buffer = FALSE) {
        $region = NULL;
        $this->render($region, $buffer);
    }

    // --------------------------------------------------------------------

    /**
     * Build a region from it's contents. Apply wrapper if provided
     *
     * @access	private
     * @param	string	region to build
     * @param	string	HTML element to wrap regions in; like '<div>'
     * @param	array	Multidimensional array of HTML elements to apply to $wrapper
     * @return	string	Output of region contents
     */
    function _build_content($region, $wrapper = NULL, $attributes = NULL) {
        $output = NULL;

        // Can't build an empty region. Exit stage left
        if (!isset($region['content']) or !count($region['content'])) {
            return FALSE;
        }

        // Possibly overwrite wrapper and attributes
        if ($wrapper) {
            $region['wrapper'] = $wrapper;
        }
        if ($attributes) {
            $region['attributes'] = $attributes;
        }

        // Open the wrapper and add attributes
        if (isset($region['wrapper'])) {
            // This just trims off the closing angle bracket. Like '<p>' to '<p'
            $output .= substr($region['wrapper'], 0, strlen($region['wrapper']) - 1);

            // Add HTML attributes
            if (isset($region['attributes']) && is_array($region['attributes'])) {
                foreach ($region['attributes'] as $name => $value) {
                    // We don't validate HTML attributes. Imagine someone using a custom XML template..
                    $output .= " $name=\"$value\"";
                }
            }

            $output .= ">";
        }

        // Output the content items.
        foreach ($region['content'] as $content) {
            $output .= $content;
        }

        // Close the wrapper tag
        if (isset($region['wrapper'])) {
            // This just turns the wrapper into a closing tag. Like '<p>' to '</p>'
            $output .= str_replace('<', '</', $region['wrapper']) . "\n";
        }

        return $output;
    }

}

// END Template Class

/* End of file Template.php */
/* Location: ./system/application/libraries/Template.php */